iT邦幫忙

2024 iThome 鐵人賽

DAY 23
1
JavaScript

TypeScript 初學者也能看的學習指南系列 第 23

TypeScript 初學者也能看的學習指南 23 - Generics 泛型 X 泛型函式

  • 分享至 

  • xImage
  •  

https://ithelp.ithome.com.tw/upload/images/20241003/20149362nZRrCNRRnN.png
本篇要來介紹泛型,由於「泛型」在 TypeScript 中的內容蠻多的!!所以會拆成多篇來介紹
Day24 - Generics 泛型 X 泛型約束

大綱

  • 泛型是什麼?
  • 泛型使用方法
  • 範例1: 泛型函式
  • 範例2: 多個參數的情況
  • 泛型圖解
  • 牛刀小試

回顧

再進入正題之前,先來回顧一下「陣列」設定型別的方式
🔗 文章傳送門:Day 06 - Array

let numbers: number[] = [1, 2, 3];               // number[] 表示一個數字型別陣列
let strings: string[] = ["hello", "TypeScript"]; // string[] 表示一個字串型別的陣列

除此之外,還有哪些定義方式呢?

那就是 陣列泛型(Array Generic)
可以使用陣列泛型 Array<元素型別> 來替換

let numbers: Array<number> = [1, 2, 3];
let strings: Array<string> = ["hello", "TypeScript"];  

其實在前面的章節,早就有接觸過泛型了,只是沒有講到很深入而已

Generics 泛型

泛型是什麼?

「泛型」是 TypeScript 中獨有的型別,它允許我們在函式、介面、Class,對相關參數的型別進行「參數化」。
這也代表我們可以透過「泛型」來建立一個更靈活的函式或其他類型的實體,用於多種數據類型而不失去型別安全性

你可以把泛型想成一個「萬用型」的工具 🔧🔧🔧

泛型使用方法

當你使用泛型時,不需要事先指定具體的型別,只需要在使用的時候指定型別即可,適用於任何你指定的數據類型
泛型會很常看到 T ,它是一個型別參數(Type Parameter),可代進任意輸入的型別,此外,也可以自由命名,等等下面會再提到

範例1: 泛型函式

泛型的基本語法是用「角括號」去包裹「型別參數」,如:<T>

function hello<T>(data: T) {
  console.log(data);
};

hello<string>("Jack");   // ✅ 印出 Jack,data 參數型別會被代入為 string
hello<string>(123);      // ❌ 型別錯誤
hello<number>(123)       // ✅ 印出 123,data 參數型別會被代入為 number

上面的範例,函式 hello 後添加了 <T>,用來代入任何型別,T 也可以叫做其它名字,例如 U

型別參數 - 命名慣例

型別參數 T 的名稱可以自由取名。目前 T 已成一種約定成俗的慣例了,T 就是「Type」

  • T - 用來表示一個通用的型別
  • U, V, W - 如果有多個型別參數,可使用這些字母來區分
  • K - Key,代表「鍵」的型別
  • V - Value,代表「值」的型別,常與K一起用於物件中
  • E - Element,代表元素的型別
  • R - Return,代表回傳型別

你也可以選擇更具體、特定的名字,例如:你正在處理使用者的相關資料,可以將參數命名為 <User>

看看下面的範例
函式 areItemsEqual 用於比較兩個物品是否相同,參數 <ItemType> 能夠更清楚表達它的用途和意義,有助於其他開發者更好理解和維護

function areItemsEqual<ItemType>(item1: ItemType, item2: ItemType): boolean {
  return item1 === item2;
}

T 也可用在回傳值上喔~

function hello<T>(data: T): T {
    return data;
};

範例2: 多個參數的情況

上面有提到,如果有多個參數,可優先使用 U, V, W 這些字母來區分
本範例使用 <T, U>

// `T` 和 `U` 是型別參數,表示兩個不同的型別
function combine<T, U>(first: T, second: U): [T, U] {
    return [first, second];
}

// 👍🏻 推薦:可以使用型別推論寫法
const a = combine("world", 123);
console.log(a); // [world, 123]

// 也可以使用型別註釋
const b = combine<string, number>("world", 123);
console.log(b); // [world, 123]

// ❌ 故意出錯的型別註釋
const c = combine<string, string>("world", 123);

初學的人可能會被這函式奇異的樣貌給嚇到,在這個例子中

  • TU 是型別參數,表示兩個不同的型別
  • combine 函式接受兩個參數:first 是型別 T,second 是型別 U
  • 回傳一個包含兩個元素的元組(Tuple),每個元素都保持其原始型別

泛型圖解

第一次接觸到泛型的時候,覺得它看起來不怎麼討喜XD 我可能有恐角括號症候群,心想為何要那麼多角括號和型別參數的代稱,一下 T 啊,一下 U 啊...
但慢慢的發現其實泛型有它的美好、彈性之處,雖然長得不好看(?
身為圖像人,來分享之前泛型的圖解

我們就以此範例程式碼為例子

function hello<T>(data: T) {
  console.log(data);
};

hello<string>("Jack");   // ✅ 印出 Jack,data 參數型別會被代入為 string
hello<string>(123);      // ❌ 型別錯誤
hello<number>(123)       // ✅ 印出 123,data 參數型別會被代入為 number

part1: 泛型函式生成(編譯時)

https://ithelp.ithome.com.tw/upload/images/20241003/201493626zf77USUqq.png

part2: 泛型的實例化(運行時)

https://ithelp.ithome.com.tw/upload/images/20241003/20149362BbfscdZJsJ.png

繪製軟體:excalidraw

牛刀小試

你會怎麼重構這段呢?

function processStringArray(strings: string[]): string[] {
    return strings;
};

function processNumberArray(numbers: number[]): number[] {
    return numbers;
};

let strAry = ["apple", "banana", "cherry"];
let numberAry = [1, 2, 3];

processStringArray(strAry)
processNumberArray(numberAry)

每天的內容有推到 github 上喔

References


上一篇
TypeScript 初學者也能看的學習指南 22 - Conditional Types 條件型別
下一篇
TypeScript 初學者也能看的學習指南 24 - Generics 泛型 X 泛型約束
系列文
TypeScript 初學者也能看的學習指南30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言